package cn.uncode.rpc.cluster;



import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import cn.uncode.rpc.core.DefaultResponse;
import cn.uncode.rpc.core.Invoker;
import cn.uncode.rpc.core.Request;
import cn.uncode.rpc.core.Response;
import cn.uncode.rpc.core.URL;
import cn.uncode.rpc.core.URLParam;
import cn.uncode.rpc.exception.AbstractException;
import cn.uncode.rpc.exception.ClusterException;
import cn.uncode.rpc.spi.SpiMeta;
import cn.uncode.rpc.util.CollectionUtil;
import cn.uncode.rpc.util.ExceptionUtil;
import cn.uncode.rpc.util.InvokerUtil;


@SpiMeta(name = "default")
public class DefaultCluster<T>  implements Cluster<T>{

    private HaStrategy<T> haStrategy;

    private LoadBalance<T> loadBalance;

    private List<Invoker<T>> invokers;

    private AtomicBoolean available = new AtomicBoolean(false);

    private URL url;

    public void init() {
        onRefresh(invokers);
        available.set(true);
    }

    public Class<T> getInterface() {
        if (invokers == null || invokers.isEmpty()) {
            return null;
        }

        return invokers.get(0).getInterface();
    }

    public Response invoke(Request request) {
        if (available.get()) {
            try {
                return haStrategy.call(request, loadBalance);
            } catch (Exception e) {
                return callFalse(request, e);
            }
        }
        return callFalse(request, new ClusterException("Provider not found"));
    }

    public String desc() {
        return toString();
    }

    public void destroy() {
        available.set(false);
        for (Invoker<T> invoker : this.invokers) {
        	invoker.destroy();
        }
    }

    public URL getUrl() {
        return url;
    }

    public void setUrl(URL url) {
        this.url = url;
    }

    public boolean isAvailable() {
        return available.get();
    }

    public String toString() {
        return "cluster: {" + "ha=" + haStrategy + ",loadbalance=" + loadBalance +
                "invokers=" + invokers + "}";

    }

    public synchronized void onRefresh(List<Invoker<T>> invokers) {
        if (CollectionUtil.isEmpty(invokers)) {
            return;
        }

        loadBalance.onRefresh(invokers);
        List<Invoker<T>> oldInvokers = this.invokers;
        this.invokers = invokers;
        haStrategy.setUrl(getUrl());

        if (oldInvokers == null || oldInvokers.isEmpty()) {
            return;
        }

        List<Invoker<T>> delayDestroyReferers = new ArrayList<Invoker<T>>();

        for (Invoker<T> invoker : oldInvokers) {
            if (invokers.contains(invoker)) {
                continue;
            }

            delayDestroyReferers.add(invoker);
        }

        if (!delayDestroyReferers.isEmpty()) {
        	InvokerUtil.delayDestroy(delayDestroyReferers);
        }
    }

    public AtomicBoolean getAvailable() {
        return available;
    }

    public void setAvailable(AtomicBoolean available) {
        this.available = available;
    }

    public HaStrategy<T> getHaStrategy() {
        return haStrategy;
    }

    public void setHaStrategy(HaStrategy<T> haStrategy) {
        this.haStrategy = haStrategy;
    }

    public LoadBalance<T> getLoadBalance() {
        return loadBalance;
    }

    public void setLoadBalance(LoadBalance<T> loadBalance) {
        this.loadBalance = loadBalance;
    }

    public List<Invoker<T>> getInvokers() {
        return invokers;
    }

    protected Response callFalse(Request request, Exception cause) {

        // biz exception 无论如何都要抛出去
        if (ExceptionUtil.isBizException(cause)) {
            throw (RuntimeException) cause;
        }

        // 其他异常根据配置决定是否抛，如果抛异常，需要统一为MotanException
        if (Boolean.parseBoolean(getUrl().getParameter(URLParam.THROW_EXCEPTION.getName(), URLParam.THROW_EXCEPTION.getValue()))) {
            if (cause instanceof AbstractException) {
                throw (AbstractException) cause;
            } else {
            	ClusterException clusterException =
                        new ClusterException(String.format("ClusterSpi Call false for request: %s", request), cause);
                throw clusterException;
            }
        }

        return buildErrorResponse(request, cause);
    }

    private Response buildErrorResponse(Request request, Exception motanException) {
        DefaultResponse rs = new DefaultResponse();
        rs.setException(motanException);
        rs.setRequestId(request.getRequestId());
        return rs;
    }

	@Override
	public int activeInvokeCount() {
		// TODO Auto-generated method stub
		return 0;
	}

}
